HTTP server (Vortex)
What this page is
Vortex is Flow-Wing’s HTTP library: you bring vortex, start a vortex::Server, listen on a port, then accept requests and send a response. Most calls that can fail return Err::Result, so you usually bring Err too. You need a working compiler install—see Getting Started → Installation.
In five steps (mental model)
bring vortexandbring Errnew vortex::Server()andapp.listen(8080)— iflistenfails, print the error and stopapp.accept()gives you a request and a response for one client- Branch on
req.getMethod()andreq.getPath()to decide what to return - Use
res.status(code).send("…")(or.json(...)when you add JSON) to answer
A real service keeps a loop around accept() and handles many requests. The example below uses one request so the program stays short; you can add while / for the same way as in LatestTests/ServerTests (see the bottom of this page).
Minimal example
Save as something like main.fg, then compile and run it (next section). This handles a single GET for /; anything else returns 404.
bring vortex
bring Err
fun fg_main() -> nthg {
var app: vortex::Server = new vortex::Server()
var err: Err::Result = app.listen(8080)
if Err::isErr(err) {
println(err.getMessage())
return :
}
/; One request for a short demo-use a loop in production
var req: vortex::Request, res: vortex::Response = app.accept()
if req == null {
println("Failed to accept connection")
return :
}
if req.getMethod() == "GET" && req.getPath() == "/" {
res.status(200).send("Hello from Vortex")
} else {
res.status(404).send("Not found")
}
}
fg_main()
return : syntaxIn functions that return nthg, use return : (with a colon) for early exit. The colon distinguishes it from value-returning return expr. Both listen() failure and accept() failure examples above use return : to bail out early without producing a value.
Run it
-
Build a native executable with your AOT compiler, same as Hello World:
flowwing main.fg -o vortexdemo./vortexdemoOn Windows, use
flowwing/FlowWing.exe, and-o vortexdemo.exe. -
In another terminal, call the server (or open
http://127.0.0.1:8080/in a browser while the program is running):curl -s http://127.0.0.1:8080/ -
JIT: if you have
flowwing-jit, you can runflowwing-jit main.fgin one step instead of-o(see Flow-Wing CLI).
bring vortex is usually enough for the linker. A -S / --server switch exists on some builds as a link hint; you typically do not need it when your source already has bring vortex. Use --help on the binary you have.
What to add next (still beginner-friendly)
- More paths — add
if/else ifongetPath()andgetMethod()(for example an/api/…JSON handler). - JSON —
bring json, build orjson::parsea payload, return withres.status(200).json(node)when your program needs an API. - Static or HTML files —
bring file, read withfile::readText, and send text or HTML insend;file::__DIR__is handy for files next to your.fg(see the larger ServerTests demo below). - Maps for small in-memory data —
bring map, orvecfor lists—same as the rest of the language.
Handling many requests at once
accept() returns one request and your loop handles it. If that handler is
slow, the next accept() waits behind it and requests queue up.
spawn the handler and the loop goes straight back to accepting:
bring vortex
bring Err
bring sys
fun handle(req: vortex::Request, res: vortex::Response) -> nthg {
sys::sleep(300) /; slow work: a database call, an LLM, anything
res.status(200).send("done\n")
}
fun fg_main() -> nthg {
var app: vortex::Server = new vortex::Server()
var err: Err::Result = app.listen(8080)
if Err::isErr(err) {
println(err.getMessage())
return:
}
for var i: int = 0 to 4 {
var req: vortex::Request, res: vortex::Response = app.accept()
spawn handle(req, res) /; <- the whole difference
}
}
fg_main()
Five requests against that server, each needing 300 ms of work:
without spawn | with spawn | |
|---|---|---|
| request 0 | 302 ms | 302 ms |
| request 1 | 607 ms | 302 ms |
| request 2 | 911 ms | 302 ms |
| request 3 | 1212 ms | 302 ms |
| request 4 | 1516 ms | 302 ms |
Same server, same work, same single thread. req and res are objects, so
the spawned task receives pointers to the same instances.
Vortex runs on libuv. One connection costs one socket on a shared event
loop — not a thread — so a server holding many idle connections costs almost
nothing. While accept() waits, the thread sleeps in the kernel and other
tasks keep running.
Limits and timeouts
The server enforces these so one client cannot exhaust memory or hold a connection open forever. Override any of them in the environment:
| Variable | Default | Controls |
|---|---|---|
FW_HTTP_MAX_BODY_KB | 8192 (8 MB) | Request body cap. Larger bodies get 413 and never reach your handler |
FW_HTTP_MAX_HEADER_KB | 32 | Header cap. Over-long headers get 431 |
FW_HTTP_IDLE_TIMEOUT_MS | 30000 | A connection that sends nothing is dropped after this |
FW_HTTP_MAX_BODY_KB=512 ./myserver
Making requests: vortex::Client
The client posts a body and streams the response back. It runs on the same event loop, so several requests from several tasks share one thread.
bring vortex
fun fetch(id: int) -> nthg {
var c: vortex::Client = new vortex::Client("http://127.0.0.1:8080/api", `{"q":1}`)
var ok: bool = c.isOk() /; read the status BEFORE close()
var body: str = ""
while !c.isDone() {
var chunk: str = c.readChunk()
if chunk != "" {
body = body + chunk
}
}
c.close()
println("request ", id, " ok=", ok)
}
for var i: int = 0 to 4 {
spawn fetch(i) /; five requests, one thread
}
close() releases the request. Read isOk() before calling it.
| Variable | Default | Controls |
|---|---|---|
FW_HTTP_CLIENT_CONNECT_MS | 5000 | Connection timeout |
FW_HTTP_CLIENT_READ_MS | 120000 | Read timeout, restarted by each chunk |
The client speaks plain HTTP. There is no TLS, so https:// URLs are
rejected — put a reverse proxy in front for public traffic.
Request and response (API cheat sheet)
vortex::Server:listen(port)→Err::Result. On success, callaccept()when you are ready.accept()— returnsvortex::Requestandvortex::Response.- Request —
getMethod(),getPath(),getBody(), and related accessors (for headers and bodies on POST/PUT). - Response — chained style:
status(n).send("text"),json(...)for JSON bodies, and streaming helpers (streamBegin/streamWrite/streamEnd) where the module provides them. Prefer the chained API over a single “options” object. - Tighten timeouts, TLS, and error handling for anything exposed on a public network. This page is a starting point, not a production checklist.
Deeper: patterns and repository examples (contributors & curious readers)
The Flow-Wing repository keeps regression and demo programs under tests/fixtures/LatestTests/ServerTests/:
| Path | What it shows |
|---|---|
vortex_router.fg | Several GET/POST routes, JSON via json::parse / .json, and text streaming (chunked .streamWrite)—good template for a small API or router. The file uses a fixed test port; pick your own for local runs. |
mission_control_server/mission_control.fg | Bigger HTML example: templates on disk, file::__DIR__, classes, text::Text, and file::readText to load .html next to the source—useful if you are building a page-style app. |
Those paths are for people who have cloned the repo; you do not need them to follow the minimal example at the top. They are not a separate “engine” product—just sample .fg you can read and borrow from.
See also
- Blog — Creating a server (short walkthrough in this site’s blog)
- Language Fundamentals → Flow-Wing CLI — flags and
--entry-point - Standard modules are case-sensitive; the import is always
bring vortexin lowercase